package Zim::Repository::Base;

use strict;
use Zim::Page;

our $VERSION = '0.17';

=head1 NAME

Zim::Repository::Base - Repository base class

=head1 DESCRIPTION

This is a base class for modules implementing handlers for L<Zim::Repository>.
It documents the interface expected to handle page objects.
It provides a number of stub methods and some logic that is common to most 
repository handlers.

When implementing a new handler start with C<get_page()>.

Note that when a page name is given it is always given fully-specified,
so you need to take into account your own namespace prefix.
All methods except for C<get_page()> and C<resolve_page> take page
objects as arguments. Therefor when you have to do an expensive lookup
to locate the contents of a page, like a recursive case-insensitive filename
lookup, this lookup should be done only once and then the result is stored in
the page object. The page object can be considered a pointer for the contents.

It is good practice to throw an exception when a method fails.
This way the GUI knows that something went wrong and can alert the user.

=head1 METHODS

=over 4

=item new(PARENT, NAMESPACE, ...)

Simple object constructor.
PARENT can be a parent repository object or undef.
NAMESPACE is the prefix for all pages managed by this repository.
Extra arguments are passed on to C<init()>.

=cut

sub new {
	my $class = shift;
	my $parent = shift;
	my $namespace = shift;
	my $index = $namespace;
	$index =~ s/:*$//;
	my $self = bless {
		parent => $parent,
		namespace => $namespace,
		index => $index,
	}, $class;
	$self->init(@_);
	return $self;
}

=item C<init(KEY => VALUE)>

Simple init function, to be overloaded.

The default expects a hash with attributes which are stored in the object hash.

=cut

sub init {
	my $self = shift;
	%$self = (%$self, @_);
}

=item C<has_pages(NAMESPACE)>

This method should return boolean if NAMESPACE exists.

=item C<list_pages(NAMESPACE)>

This method should return a list of pages in NAMESPACE.
The list is used by the gui to produce a hierarchical index,
it does not tell anything about the actual existence of the pages.

The default returns an empty list.

=cut

sub has_pages { scalar $_[0]->list_pages() }

sub list_pages { }

=item C<get_page(NAME)>

This method is expected to return a page object for NAME.
Page objects should inherit from L<Zim::Page>.
See L<Zim::Page::Text> for an example.

When a page does not exist an empty object should be returned
that can be used to create this page by saving to it.
The status of this object should be set to 'new'.
When a page does not exists and can not be created undef should
be returned.

The default does nothing.

=cut

sub get_page { }

=item C<resolve_page(NAME)>

This method is a frontend for C<get_page()> that is used when
NAME is user input. This means that it should probably do a 
case-insensitive lookup and should expect things like typing errors,
characters that are not allowed etc.

This stub just calls C<get_page()>.

=cut

sub resolve_page { $_[0]->get_page($_[1]) }

=item C<copy_page(SOURCE, TARGET)>

Copy contents of object SOURCE to object TARGET.
Both page objects should belong to this repository.

Make sure to update the page objects correctly.
For example set status and update or flush the parse tree.

=cut

sub copy_page {
	my ($self, $source, $target) = @_;
	$target->clone($source);
}

=item C<move_page(SOURCE, TARGET)>

Move the content of object SOURCE to object TARGET.
Both page objects should belong to this repository.

Make sure to update the page objects correctly.
For example set status and update or flush the parse tree.

The default just calls C<copy_page()> and C<delete_page()>.

=cut

sub move_page {
	my ($self, $source, $target) = @_;
	$self->copy_page($source, $target);
	$source->delete;
}

=item C<delete_page(PAGE)>

Delete object PAGE and returns the page object.
Be aware that although the content is deleted the PAGE object
goes on living and should be updated accordingly.
The status of the object should be set to 'deleted'.

The default method fails silently.

=cut

sub delete_page { }

=item C<resolve_link(PAGE, LINK)>

Returns a page name for a link. The link might be relative to the page.
This method calls C<< root->resolve_page() >> automaticly.

The logic implemented here is a sensible default which does an "intelligent"
upward lookup.

=cut

sub resolve_link {
	my ($self, $page, $link) = @_;
	my ($name, $namespace) = ($page->name, $page->namespace);
	my $root = $self->root;

	#warn "Resolving $link\n";
	$link =~ s/:+$//;

	if    ($link =~ s/^\.//) { $link = $name.':'.$link } # sub namespace
	elsif ($link !~ /^:/)    { # upward search
		my ($match, $postfix) = ('', '');
		my @ns = ('', $page->namespaces);
		while ($link) {
			for (reverse 0 .. $#ns) {
				my $test = join ':', @ns[0 .. $_], $link;
				#warn "Trying $test\n";
				my $p = $root->resolve_page($test);
				next unless $p->exists()
				     or     $root->has_pages($p->name . ':') ;
				# FIXME more efficient lookup which also returns true for dirs
				# FIXME using a cache will also help
				$match = $test;
				last;
			}
			last if length $match;
			$link =~ s/:*([^:]*)$//; # pop
			$postfix = $1 . ':' . $postfix;
		}
		$match = $namespace unless length $match;
		$link = $match . ':' . $postfix ;
		$link =~ s/:+$//;
	}

	#warn "Resulted in $link\n";
	return $self->root->resolve_page($link); # does the cleanup
}

=back

=head2 Utility methods

These methods are commonly used methods in repository objects.
They do not need to be overloaded.

=over 4

=item C<root()>

Returns the top parent repository object. This is used to get pages when
we are not sure these pages belong to our repository.

=cut

sub root {
	my $self = shift;
	my $obj = $self;
	while (defined $obj->{parent}) {
		$obj = $obj->{parent};
		last if $obj eq $self; # prevent infinite loop
	}
	return $obj;
}

=item C<wipe_array(REF)>

Removes double items from the array refered to by ref.

=cut

sub wipe_array {
	my $ref = pop;
	@$ref = sort @$ref;
	my $prev = '';
	for (@$ref) {
		if ($_ eq $prev) { $_ = undef }
		else             { $prev = $_ }
	}
	@$ref = grep defined($_), @$ref;
	return $ref;
}

*interwiki_lookup = \&Zim::Repository::interwiki_lookup;

1;

__END__

=back

=head1 BUGS

Please mail the author if you find any bugs.

=head1 AUTHOR

Jaap Karssenberg || Pardus [Larus] E<lt>pardus@cpan.orgE<gt>

Copyright (c) 2006 Jaap G Karssenberg. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 SEE ALSO

L<Zim>, L<Zim::Repository>

=cut
